package ocbn.remotelab.xml;

import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.util.Vector;
import ocbn.remotelab.events.Event;
import ocbn.remotelab.events.MassSpectrometerStateEvent;
import ocbn.remotelab.util.NumberConverter;
import ocbn.remotelab.xml.helpers.AttributesBuilder;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * A class that exports a Vector of Events and converts it to XML. It uses SAX
 * to export XML. 
 *
 * @author Ke Li
 */
public class XMLExport {

    /**
     * Exports the compact XML representation (without unnecessary whitespaces)
     * of a Vector of Events
     *
     * @param events    A Vector of Events to be exported as XML
     * @return          A String containing the compact XML representation of
     *                  the Vector of Events
     */

    public static String exportXML(Vector events){
        return exportXML(events, true);
    }

    /**
     * Exports the XML representation of a Vector of Events. One has the
     * flexibility to choose to output either a easily readable version or a
     * compact version without unnecessary whitespaces by specifying the second
     * parameter. 
     *
     * @param events    A Vector of Events to be exported as XML
     * @param compact   A boolean indicating whether the XML should be compact
     *                  (without unnecessary whitespaces)
     * @return          A String containing the XML representation of the Vector
     *                  of Events
     */

    public static String exportXML(Vector events, boolean compact){
        OutputStream output = new ByteArrayOutputStream();
        XMLWriter writer = new XMLWriter(output);
        AttributesBuilder attributesBuilder;
        Event currentEvent;
        if (compact) {
            writer.setPrintCompressedOutput();
        } else {
            writer.setPrettyPrint();
        }
        try {
            // Export the headers of an XML document
            writer.startDocument("UTF-8");
            writer.startPrefixMapping("xml",
                    "http://www.w3.org/XML/1998/namespace");
            // The root element
            writer.startElement("data");
            // Iterate through all the events and export them one by one
            for(int i = 0; i < events.size(); i++) {
                currentEvent = (Event)events.elementAt(i);
                if(currentEvent.getType() == MassSpectrometerStateEvent.class) {
                    // Add an "event" element and set the "type" attribute
                    attributesBuilder = new AttributesBuilder();
                    attributesBuilder.addAttribute("type", "MS State");
                    writer.startElement("event",
                            attributesBuilder.getAttributes());
                } else {
                    writer.startElement("event");
                }
                // Add all the elements that represent the properties of the
                // current Event into the "event" element
                writer.addElement("id",
                        NumberConverter.toHexString(currentEvent.getID()));
                writer.addElement("time", currentEvent.getTime().toString());
                if(currentEvent.getType() == MassSpectrometerStateEvent.class) {
                    MassSpectrometerStateEvent currentMSEvent
                            = (MassSpectrometerStateEvent)currentEvent;
                    writer.addElement("state",
                            currentMSEvent.getState().name().toLowerCase());
                    writer.addElement("source", currentMSEvent.getSource());
                    if(currentMSEvent.hasDetails()) {
                        writer.addElement("details",
                                currentMSEvent.getDetails());
                    }
                } else {
                    if(currentEvent.isImportant()) {
                        writer.addElement("important");
                    }
                }
                writer.endElement("event");
            }
            writer.endElement("data");
            writer.endPrefixMapping("xml");
            writer.endDocument();
        } catch (SAXException e) {
            System.err.println("XML Writing Error");
        }
        return output.toString();
    }

    /**
     * A private inner class that extends the proprietary XMLWriter class by
     * RIM. It overloads some of the methods provided by RIM to either add
     * flexibility or convenience. 
     */

    private static class XMLWriter extends
            net.rim.device.api.xml.jaxp.XMLWriter {

        /**
         * The output stream to which the XML will be written
         */

        OutputStream outputStream;

        /**
         * Constructs a new instance from the output stream to which the XML
         * will be written.
         *
         * @param out   The output stream to which the XML will be written
         */

        public XMLWriter(OutputStream out) {
            super(out);
            outputStream = out;
        }

        /**
         * Starts the XML document (by adding a header). Allows one to specify
         * the encoding of the document.
         *
         * @param encoding          The encoding of the XML document
         * @throws SAXException     Indicates that the supplied output stream
         *                          in the constructor cannot be written to
         */

        private void startDocument(String encoding) throws SAXException {
            String header = "<?xml version=\"1.0\" encoding=\"" + encoding
                    + "\"?>";
            try {
                outputStream.write(header.getBytes());
            } catch (IOException e) {
                throw new SAXException("Cannot write XML to the output stream");
            }
        }

        /**
         * Starts an element (by adding an opening tag) that has no attributes.
         * Allows one to specify the element (tag) name.
         *
         * @param nodeName          The name of the element/tag
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void startElement(String nodeName) throws SAXException {
            startElement("", nodeName, nodeName,
                    AttributesBuilder.getEmptyAttributes());
        }

        /**
         * Starts an element (by adding an opening tag) that may have 
         * attributes. Allows one to specify the element (tag) name and the
         * attributes of the element.
         *
         * @param nodeName          The name of the element/tag
         * @param attributes        An Attributes object representing the
         *                          attributes of this element. An Attributes
         *                          object is obtained through AttributesBuilder
         *                          class' getAttributes method. 
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void startElement(String nodeName, Attributes attributes)
                throws SAXException {
            startElement("", nodeName, nodeName, attributes);
        }

        /**
         * Ends an element (by adding an closing tag). 
         *
         * @param nodeName          The name of the element/tag
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void endElement(String nodeName) throws SAXException {
            endElement("", nodeName, nodeName);
        }

        /**
         * Adds a complete element (with opening and closing tags) that may have
         * text enclosed inside the element and attributes. Allows one to 
         * specify the element (tag) name, the text between the opening and 
         * closing tags and the attributes of the element. 
         *
         * @param nodeName          The name of the element/tag
         * @param value             The text enclosed inside the element
         *                          (between the opening and closing tags)
         * @param attributes        An Attributes object representing the
         *                          attributes of this element. An Attributes
         *                          object is obtained through AttributesBuilder
         *                          class' getAttributes method. 
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void addElement(String nodeName, String value, 
                Attributes attributes) throws SAXException {
            if(value.length() <= 0) {
                addElement(nodeName, attributes);
            } else {
                char[] tempCharArray = new char[value.length()];
                startElement(nodeName, attributes);
                value.getChars(0, value.length(), tempCharArray, 0);
                characters(tempCharArray, 0, value.length());
                endElement(nodeName);
            }
        }

        /**
         * Adds a complete element (with opening and closing tags) that may have
         * text enclosed inside the element, but do not have attributes. Allows
         * one to specify the element (tag) name and the text between the
         * opening and closing tags.
         * 
         * @param nodeName          The name of the element/tag
         * @param value             The text enclosed inside the element
         *                          (between the opening and closing tags)
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void addElement(String nodeName, String value)
                throws SAXException {
            addElement(nodeName, value, AttributesBuilder.getEmptyAttributes());
        }

        /**
         * Adds a complete element (with opening and closing tags) that may have
         * attributes, but do not have text enclosed inside the element. Allows
         * one to specify the element (tag) name and the attributes of the
         * element.
         *
         * @param nodeName          The name of the element/tag
         * @param attributes        An Attributes object representing the
         *                          attributes of this element. An Attributes
         *                          object is obtained through AttributesBuilder
         *                          class' getAttributes method. 
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void addElement(String nodeName, Attributes attributes)
                throws SAXException {
            startAndEndElement("", nodeName, nodeName, attributes);
        }

        /**
         * Adds a complete element (with opening and closing tags) without text
         * enclosed inside the element. Allows one to specify the element (tag)
         * name.
         * 
         * @param nodeName          The name of the element/tag
         * @throws SAXException     Indicates that the parameters that are
         *                          passed into this method caused an error
         *                          when writing XML
         */

        private void addElement(String nodeName) throws SAXException {
            addElement(nodeName, AttributesBuilder.getEmptyAttributes());
        }
    }
}
